/*
@STI LICENSE
(C) 2006-2008 Universit�t Innsbruck.

This file is part of myOntology.

myOntologyis free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

myOntologyis distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with myOntology.  If not, see <http://www.gnu.org/licenses/>.

@STI LICENSE */
package controller.elementMapper;

import java.util.ArrayList;
import java.util.GregorianCalendar;

import model.Vocabulary;
import model.dataContainer.TranslationContainer;

import org.apache.log4j.Logger;
import org.openrdf.model.URI;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.RepositoryConnection;

import controller.HelperFunctions;
import controller.sesameDataConnector.AbstractDataConnector;

public abstract class VersioningMapper {

	public static Logger logger = Logger.getLogger(VersioningMapper.class);

	/**
	 * Selects the object of the triple <uri> <mo:haspreviousversion> <object> and hence a less actual version
	 * @param uri
	 * @return
	 */
	public static URI selectOlderVersion( URI uri ) {
		URI ret = null;
		
		try {
			ret = AbstractDataConnector.selectObject( uri, Vocabulary.HASPREVIOUSVERSION );
		}
		catch( Exception e ) {
			logger.error( "Exception in selectPreviousVersion():",e );
		}
		
		return ret;		
	}
	
	/**
	 * Selects the subject of the triple <s> <mo:haspreviousversion> <uri> and hence a more actual version
	 * @param uri
	 * @return
	 */
	public static URI selectNewerVersion( URI uri ) {
		URI ret = null;
		
		try {
			ret = AbstractDataConnector.selectSubject( Vocabulary.HASPREVIOUSVERSION, uri );
		}
		catch( Exception e ) {
			logger.error( "Exception in selectNextVersion():" , e );
		}
		
		return ret;		
	}
	
	
	public static URI getChangedProperty( URI id ) {
		URI ret = null;
		
		try {
			ArrayList<URI> tmp = AbstractDataConnector.selectPredicates( id, null );			

			if( tmp!=null ) {
				java.util.Iterator<URI> iter = tmp.iterator();
				
				while( ret==null && iter.hasNext() ) {
					URI tmp2 = iter.next();
					if( 
							!tmp2.equals(org.openrdf.model.vocabulary.RDF.TYPE) &&
							!tmp2.equals(Vocabulary.HASVERSIONDATETIME ) &&
							!tmp2.equals(Vocabulary.HASPREVIOUSVERSION)
					) {
						ret = tmp2;
					}
						
				}
			}
			
			if( ret==null ) {
				
				tmp = AbstractDataConnector.selectPredicates( null, id );

				if( tmp!=null ) {
					java.util.Iterator<URI> iter = tmp.iterator();
					
					while( ret==null && iter.hasNext() ) { // can only contain 1 change!
						URI tmp2 = iter.next();
						if( 
								!tmp2.equals(org.openrdf.model.vocabulary.RDF.TYPE) &&
								!tmp2.equals(Vocabulary.HASVERSIONDATETIME ) &&
								!tmp2.equals(Vocabulary.HASPREVIOUSVERSION)
						) {
							ret = tmp2;
						}
							
					}
				}
				
			}
			
		}
		catch( Exception e ) {
			logger.error( "Exception in getChangedProperty of id " + id + ": " ,e);
		}
		
		return ret;
	}
	
	/**
	 * returns the Number of the last archived version to the actual element with URI uri.
	 * @param uri	the actual element to which the version should be found.
	 * @return
	 */
	public static URI getMostActualVersion( URI uri ) {
		
		URI ret = uri;
		
		try {	

			URI moreActualVersion = AbstractDataConnector.selectSubject( Vocabulary.HASPREVIOUSVERSION, uri );
						
			while( moreActualVersion != null ) {
				
				ret = moreActualVersion;
				
				moreActualVersion = AbstractDataConnector.selectSubject( Vocabulary.HASPREVIOUSVERSION, moreActualVersion );
			}

		} catch (Exception e) {
			logger.error("Error in getMostActualVersion " ,e);
		}
		
		return ret;
	}
	
	
	/**
	 * returns the Number of the last archived version to the actual element with URI uri.
	 * @param uri	the actual element to which the version should be found.
	 * @return
	 */
	protected static int getLastVersion( URI uri ) {
		int ret = -1;
		
		try {
			URI tmp = selectOlderVersion(uri);
			if( tmp != null ) {
				String tmpString = tmp.stringValue();
				
				tmpString = tmpString.replaceFirst( "^http.*/v", "" ); // deletes the start
				tmpString = tmpString.replace( "/.*$", "" ); // deletes the end
				
				ret = new Integer( tmpString );
			}
			// else ret = -1;
		}
		catch( Exception e ) {
			logger.error( "Exception in getLastVersion of uri " + uri.stringValue() + ": " , e );
		}
		
		return ret;
	}
	
	/**
	 * Creates a new archivedClass-URI to the element with given id
	 * @param id uri to which the archivedClass-URI should be generated.
	 * @return
	 */
	private static URI getToArchiveId( URI id ){

		int lastVersion = getLastVersion(id);
		
		URI ret = new URIImpl( id.stringValue() + "/v" + (lastVersion+1) );
		
		return ret;
	}
	

	
	private static URI addArchivedElement( RepositoryConnection con, URI id ) throws Exception {

		URI oldId = getToArchiveId( id );
		URI oldType = AbstractDataConnector.getType( id );
		URI newType = null;
		if( oldType!=null ) { 
			if( oldType.equals(Vocabulary.CLASS))
				newType = Vocabulary.ARCHIVEDCLASS;
			else if( oldType.equals(Vocabulary.ONTOLOGYMODULE))
				newType = Vocabulary.ARCHIVEDONTOLOGYMODULE;
			else if( oldType.equals(Vocabulary.DATATYPEPROPERTY))
				newType = Vocabulary.ARCHIVEDDATATYPEPROPERTY;
			else if( oldType.equals(Vocabulary.OBJECTPROPERTY))
				newType = Vocabulary.ARCHIVEDOBJECTPROPERTY;
			
			con.add(oldId, RDF.TYPE, newType );
			
			// archive HASVERSIONDATETIME:
			//archive old value
			con.add(oldId, Vocabulary.HASVERSIONDATETIME, new LiteralImpl(AbstractDataConnector.selectLiteralObject(id, Vocabulary.HASVERSIONDATETIME, null)));
			
			//remove old values 
			con.remove(id, Vocabulary.HASVERSIONDATETIME, null);
			
			//insert new values
			
			con.add(id, Vocabulary.HASVERSIONDATETIME, new LiteralImpl(HelperFunctions.convertIntoString(new GregorianCalendar().getTimeInMillis())));
			
	
			// archive HASPREVIOUSVERSION:		
			archiveHasPreviousVersion(con, oldId, id );
		}
		
		return oldId;
	}
	
	private static void archiveHasPreviousVersion( RepositoryConnection con, URI oldId, URI id ) throws Exception {

		URI previousVersion = AbstractDataConnector.selectObject(id, Vocabulary.HASPREVIOUSVERSION );
		
		//archive old value
		if( previousVersion != null ) {
			con.add(oldId, Vocabulary.HASPREVIOUSVERSION, previousVersion );		
		}
		// else: nothing should be created (value in History-Chain should not contain a triple with HASPREVIOUSVERSION) 
		
		//remove old values 
		con.remove(id, Vocabulary.HASPREVIOUSVERSION, null);	
		
		//insert new values
		con.add(id, Vocabulary.HASPREVIOUSVERSION, oldId);
	}
	
	
	


	/**
	 * 	 * Extracts the elements, which were removed/added and creates a new version of the "touched"; the triple is updated to id (archivedObjects property x)
	 * 
	 * This is useful e.g. when archiving (Properties having class in range) <p1> <range> <c1> is changed to <p1,p2> <range> <c1>; so p2 is touched and p2 should be archived (<p2> <range> <c1 and all its other old values>) 
	 * This is useful e.g. when archiving (superclass) <c1> <subclassof> <c2> is changed to <c1,c3> <subclassof> <c2>; so c3 is touched and c3 should be archived (<c3> <subclassof> <c2 and all its other old values>) 
	 * @param con
	 * @param oldList
	 * @param newList
	 * @param id
	 * @param property
	 * @throws Exception
	 */
	public static void archiveRemoveInsertObjects( RepositoryConnection con, ArrayList<URI> oldList, ArrayList<URI> newList, URI id, URI property ) throws Exception {

		// if (( oldList.size() == newList.size() && !oldList.containsAll(newList) ) || ( oldList.size() != newList.size() )) {

		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !oldList.containsAll(newList)) ) || ( newList!=null && (oldList==null || !newList.containsAll(oldList) )) ) ) {
			
			// ######### check if Elements were removed ##########
			if( oldList!=null ) {
				for (int i = 0; i < oldList.size(); i++) {

					URI toModifyId = (URI)oldList.get(i);
					
					if( newList==null || !newList.contains(toModifyId) ) {
						// element oldList.get(i) was removed by user
						
						URI archivedId = addArchivedElement( con, toModifyId );	
	
						//archive old values					
						ArrayList<URI> list = AbstractDataConnector.selectObjects( toModifyId, property, null );
						if( list==null || list.size() == 0 ) {
							//archive NULL if there were no subjects:
							con.add(archivedId, property, Vocabulary.NULL); // to mark, that the old value was not set.
						}
						else {
							java.util.Iterator<URI> iter = list.iterator();
							while( iter.hasNext() ) {
								URI next = (URI)iter.next();
								
								con.add(archivedId, property, next ); 
							}							
						}
						
						//remove old value
						con.remove( toModifyId, property, id );
					}
				}
			}
			

			// ######### check if Elements were added ##########
			if( newList!=null ) {
				for (int i = 0; i < newList.size(); i++) {

					URI toModifyId = (URI)newList.get(i);
					
					if( oldList==null || !oldList.contains(toModifyId) ) {
						// triple "ElementId belongsToOM toModifyId" was added by user; So: archive toModifyId
	
						URI archivedId = addArchivedElement( con, toModifyId );						

						//archive old values
						ArrayList<URI> list = AbstractDataConnector.selectObjects( toModifyId, property, null );
						if( list==null || list.size() == 0 ) {
							//archive NULL if there were no subjects:
							con.add( archivedId, property, Vocabulary.NULL ); // to mark, that the old value was not set.
						}
						else {
							java.util.Iterator<URI> iter = list.iterator();
							while( iter.hasNext() ) {
								URI next = (URI)iter.next();
								
								con.add( archivedId, property, next ); 
							}				
						}
						
						//insert new value 
						con.add( toModifyId, property, id );
					}
				}
			}
			
		}
	}
	
	


	/**	 
	 * Extracts the elements, which were removed/added and creates a new version of the "touched"; the triple is updated to id (archivedObjects property x)
	 * 
	 * This is useful e.g. when archiving <c1> <belongsToOm> <om1> is changed to <c1,p1> <belongsToOm> <om1>; so p1 is touched and om1 should be archived (<p1> <belongsToOm> <om1/v0>) 
	 * @param con
	 * @param oldList
	 * @param newList
	 * @param id
	 * @param property
	 * @throws Exception
	 */
	public static void archiveRemoveInsertSubject( RepositoryConnection con, ArrayList<URI> oldList, ArrayList<URI> newList, URI id, URI property ) throws Exception {

		// if (( oldList.size() == newList.size() && !oldList.containsAll(newList) ) || ( oldList.size() != newList.size() )) {

		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !oldList.containsAll(newList)) ) || ( newList!=null && (oldList==null || !newList.containsAll(oldList) )) ) ) {
			
			URI archivedId = addArchivedElement(con, id);

			if( oldList!=null && oldList.size() > 0) {
				
				if( oldList!=null) {								
					for (int i = 0; i<oldList.size(); i++) {

						//remove old values 
						con.remove( oldList.get(i), property, id );

						//archive old value
						con.add( oldList.get(i), property, archivedId );
					}
				}
				
			}
			else {
				con.add(Vocabulary.NULL, property, archivedId ); // to mark, that the old value was not set.
			}
			
			//insert new values
			if( newList!=null ) {
				for (int i = 0; i<newList.size(); i++) {

					//insert new value
					con.add( newList.get(i), property, id );
				}
			}
			
			//TODO: can be optimized as not every triple has to be removed (some will then be added again)... 
			
		}
	}
	
	

	/**
	 * Extracts the elements, which were removed/added and creates a new version of the "touched"; the triple is updated to id (x property archivedSubjects)
	 * 
	 * This is useful e.g. when archiving <p1> <Domain> <c1> is changed to <p1> <Domain> <c1,c2>; so c2 ist touched and c2 should be archived (<p1> <Domain> <c2/v0>) 
	 * @param con
	 * @param oldList
	 * @param newList
	 * @param id
	 * @param property
	 * @throws Exception
	 */
	public static void archiveRemoveInsertSubjects( RepositoryConnection con, ArrayList<URI> oldList, ArrayList<URI> newList, URI id, URI property ) throws Exception {
		
		// if (( oldList.size() == newList.size() && !oldList.containsAll(newList) ) || ( oldList.size() != newList.size() )) {

		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !oldList.containsAll(newList)) ) || ( newList!=null && (oldList==null || !newList.containsAll(oldList) )) ) ) {
			
			// ######### check if Elements were removed ##########
			if( oldList!=null ) {
				for (int i = 0; i < oldList.size(); i++) {

					URI toModifyId = (URI)oldList.get(i);
					
					if( newList==null || !newList.contains(toModifyId) ) {
						// element oldList.get(i) was removed by user
						
						URI archivedId = addArchivedElement(con, toModifyId);	
	
						//archive old values					
						ArrayList<URI> subjects = AbstractDataConnector.selectSubjects( property, toModifyId );
						if( subjects==null || subjects.size() == 0 ) {
							//archive NULL if there were no subjects:
							con.add(Vocabulary.NULL, property, archivedId); // to mark, that the old value was not set.
						}
						else {
							java.util.Iterator<URI> iter = subjects.iterator();
							while( iter.hasNext() ) {
								URI next = (URI)iter.next();
								
								con.add(next, property, archivedId);
							}							
						}
						
						//remove old value
						con.remove(id, property, toModifyId);
					}
				}
			}
			

			// ######### check if Elements were added ##########
			if( newList!=null ) {
				for (int i = 0; i < newList.size(); i++) {

					URI toModifyId = (URI)newList.get(i);
					
					if( oldList==null || !oldList.contains(toModifyId) ) {
						// triple "ElementId belongsToOM toModifyId" was added by user; So: archive toModifyId
	
						URI archivedId = addArchivedElement( con, toModifyId );						

						//archive old values
						ArrayList<URI> subjects = AbstractDataConnector.selectSubjects( property, toModifyId );
						if( subjects==null || subjects.size() == 0 ) {
							//archive NULL if there were no subjects:
							con.add(Vocabulary.NULL, property, archivedId); // to mark, that the old value was not set.
						}
						else {
							java.util.Iterator<URI> iter = subjects.iterator();
							while( iter.hasNext() ) {
								
								URI next = (URI)iter.next();
								
								con.add(next, property, archivedId); 
							}				
						}
						
						//insert new value 
						con.add( id, property, toModifyId );
					}
				}
			}
			
		}
	}
	
	/**
	 * 
	 * @param con
	 * @param oldList
	 * @param newList
	 * @param id
	 * @param property
	 * @throws Exception
	 */
	public static void archiveRemoveInsertURIs( RepositoryConnection con, ArrayList<URI> oldList, ArrayList<URI> newList, URI id, URI property ) throws Exception {
		
		// if (( oldList.size() == newList.size() && !oldList.containsAll(newList) ) || ( oldList.size() != newList.size() )) {
		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !oldList.containsAll(newList)) ) || ( newList!=null && (oldList==null || !newList.containsAll(oldList) )) ) ) {		
			URI oldId = addArchivedElement(con, id);

			//archive old value
			if( oldList!=null && oldList.size() > 0) {
				AbstractDataConnector.addURIs(con, oldId, property, oldList);
			}
			else {
				con.add(oldId, property, Vocabulary.NULL); // to mark, that the old value was not set.
			}
			
			//remove old values 
			con.remove(id, property, null);
			
			//insert new values
			if( newList!=null ) {
				AbstractDataConnector.addURIs(con, id, property, newList);
			}
		}
	}
	

	public static void archiveRemoveInsertString( RepositoryConnection con, String oldString, String newString, URI id, URI property, String lang ) throws Exception {

		String tmpOldString = oldString;
		String tmpNewString = newString;
		
		if( oldString==null)
			tmpOldString = "";		
		if( newString==null )
			tmpNewString = "";
		
		//if( !newString.equals(oldString) ) {
		if( !tmpOldString.equals( tmpNewString ) ) {
			
			URI oldId = addArchivedElement(con, id);
			
			if( lang==null){
				
				//archive old value
				if( oldString!=null )
					con.add(oldId, property, new LiteralImpl(oldString));
				else
					con.add(oldId, property, new LiteralImpl(""));
				
				//remove old values 
				con.remove(id, property, null);
				
				//insert new value
				if( newString!=null )
					con.add(id, property, new LiteralImpl(newString));
			}
			else {

				//archive old value
				if( oldString!=null )
					con.add(oldId, property, new LiteralImpl(oldString, lang));
				else
					con.add(oldId, property, new LiteralImpl("", lang));
				
				//remove old values
				con.remove(id, property, null);
				
				//insert new value
				if( newString!=null )
					con.add(id, property, new LiteralImpl(newString, lang));
			}
		}
	}
	
	public static void archiveRemoveInsertStrings( RepositoryConnection con, ArrayList<String> oldList, ArrayList<String> newList, URI id, URI property ) throws Exception {
		
		//if (!(oldList.containsAll(newList) && newList.containsAll(oldList))) {

		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !oldList.containsAll(newList)) ) || ( newList!=null && (oldList==null || !newList.containsAll(oldList) )) ) ) {
			// there are differences...

			URI oldId = addArchivedElement(con, id);

			//archive old value
			if( oldList!=null && oldList.size() > 0) {
				AbstractDataConnector.addLiterals(con, oldId, property, oldList);
			}
			else {
				con.add(oldId, property, new LiteralImpl("")); // to mark, that the old value was not set.
			}
			
			//remove old values 
			con.remove(id, property, null);
			
			//insert new values
			if( newList!=null ) {
				AbstractDataConnector.addLiterals(con, id, property, newList);
			}
		}
	}
	
	private static boolean containsAll( ArrayList<TranslationContainer> list1, ArrayList<TranslationContainer> list2 ) {

		java.util.Iterator<TranslationContainer> iter1 = list1.iterator();
		java.util.Iterator<TranslationContainer> iter2 = list2.iterator();
		boolean found1 = true;
		
		while( found1==true && iter1.hasNext() ) {
			
			boolean found2 = false;
			
			while( found2==false && iter2.hasNext() ) {
				if( iter1.next().equals( iter2.next() ) ) {
					found2 = true;
				}
			}
			
			found1 = found2;			
		}
		
		return found1;		
	}
	
	public static void archiveRemoveInsertTranslations( RepositoryConnection con, ArrayList<TranslationContainer> oldList, ArrayList<TranslationContainer> newList, URI id ) throws Exception {
		
		if( (oldList!=null || newList!=null) && ( oldList!=null && ( (newList==null || !containsAll( oldList, newList )) ) || ( newList!=null && (oldList==null || !containsAll( newList, oldList ) )) ) ) {
			
			URI oldId = addArchivedElement(con, id);

			//archive old value
			if( oldList!=null && oldList.size() > 0) {

				AbstractDataConnector.addTranslations(con, oldId, oldList );
			}
			else {
				con.add(oldId, Vocabulary.TRANSLATION, new LiteralImpl( "" ) ); // to mark, that the old value was not set.
			}

			//remove old values 
			con.remove(id, Vocabulary.TRANSLATION, null);

			//insert new values
			if( newList!=null ) {
				AbstractDataConnector.addTranslations(con, id, newList );
			}
		}
	}
	
}
